package com.zeng.ssm.service.support.trans.base;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.CharsetUtil;
import com.zeng.ssm.api.support.idempotent.DistributedLockService;
import com.zeng.ssm.dal.mapper.AccountLogMapper;
import com.zeng.ssm.dal.mapper.AccountMsgLogMapper;
import com.zeng.ssm.dal.model.AccountLog;
import com.zeng.ssm.dal.model.AccountMsgLog;
import com.zeng.ssm.dal.model.AccountMsgLogExample;
import com.zeng.ssm.service.support.trans.GxpConstant;
import com.zeng.ssm.service.support.trans.TransConstant;
import com.zeng.ssm.service.support.trans.annotation.ClientHandler;
import com.zeng.ssm.service.support.trans.annotation.ServerHandler;
import com.zeng.ssm.service.support.trans.base.model.IBody;
import com.zeng.ssm.util.DateUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.List;
import java.util.UUID;

/**
 * 服务端-处理Handler
 *
 * @author zengwanlin
 * @date 2023-08-30T16:11
 */
public abstract class BaseServerHandler implements IServerHandler {

    private Logger logger = LoggerFactory.getLogger(BaseServerHandler.class);

    @Autowired
    private DistributedLockService distributedLockService;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private AccountLogMapper accountLogMapper;

    @Autowired
    private AccountMsgLogMapper accountMsgLogMapper;

    /**
     * 交易组件
     */
    protected BasicTransComponent component;

    /**
     * 上一次交易记录
     */
    private AccountLog lastAccountLog;

    /**
     * 上一次交易日志记录
     */
    private AccountMsgLog lastAccountMsgLog;

    /**
     * 当前交易记录
     */
    private AccountLog currAccountLog;

    /**
     * 当前交易日志记录
     */
    private AccountMsgLog currAccountMsgLog;

    /**
     * 作为服务端，是否在业务处理前
     * <p>
     * true-处理前，false-已进入业务处理
     */
    private boolean beforeDoService;

    /**
     * 交易日期
     */
    protected final String tranDate = DateUtil.getDate8();

    /**
     * 交易时间
     */
    protected final String tranTime = DateUtil.getTime6();

    /**
     * 作为服务端-触发业务调用
     *
     * @param transComponent
     */
    @Override
    public void service(BasicTransComponent transComponent) {
        // 初始化交易组件
        logger.info("初始化交易组件");
        init(transComponent);

        ImmutablePair<String, String> lockPair = null;
        try {
            // 解析请求报文
            logger.info("解析请求报文");
            String lockKey = parseRequestMsg();
            Assert.hasText(component.getReqSeqNo(), "请求流水号未设置");
            Assert.notNull(component.getReqMessage(), "请求实体类未设置");
            Assert.notNull(component.getContractId(), "批次ID未设置");
            Assert.notNull(component.getDetailId(), "明细ID未设置");
            Assert.hasText(component.getProductNo(), "产品号未设置");
            if (component.isReversal() && StringUtils.isEmpty(component.getOrgReqSeqNo())) {
                throw new IllegalArgumentException("冲正交易，原请求流水号未设置");
            }

            // 记录交易日志
            logger.info("记录交易日志");
            writeAccountLog();

            // 加锁
            if (StringUtils.isNotBlank(lockKey)) {
                logger.info("请求加锁");
                lockPair = lock(lockKey);
            }

            // 防重检查
            if (component.isUnRepeatable()) {
                logger.info("防重检查");
                if (succeed()) {
                    // 当前已成功
                    if (component.isReParse()) {
                        // 需要再次解析响应报文
                        reParseRequestMsg();
                    }
                    component.setSuccess(true);
                    component.setRetMsg("已有成功记录");
                    return;
                }
            }

            // 业务处理
            logger.info("业务处理");
            IBody responseBody = doService();
            this.beforeDoService = false;
            component.setSuccess(true);
            component.setRetMsg(GxpConstant.RET_MSG_SUCCESS);

            // 组装响应报文
            logger.info("组装响应报文");
            buildResponseMsg(responseBody);

            // 更新交易日志
            logger.info("更新交易日志");
            updateAccountLog(null);

        } catch (Throwable th) {
            logger.info("异常，更新交易日志");
            component.setSuccess(false);
            component.setRetMsg(ExceptionUtils.getRootCauseMessage(th));
            updateAccountLog(th);
            logger.error("交易异常", th);
            buildResponseMsg(null);
        } finally {
            // 解锁
            if (lockPair != null) {
                logger.info("请求解锁");
                unlock(lockPair);
            }
        }
    }

    /**
     * 初始化
     *
     * @param transComponent
     */
    protected void init(BasicTransComponent transComponent) {
        // 参数检查
        Assert.hasText(transComponent.getReqMsg(), "请求报文未设置");

        this.beforeDoService = true;

        ServerHandler annotation = getClass().getDeclaredAnnotation(ServerHandler.class);
        if (annotation == null) {
            throw new IllegalArgumentException("类" + getClass().getName() + "未标注注解：@" + ServerHandler.class.getSimpleName());
        }
        if (annotation.reParse() && ServerHandler.LoggingType.NONE.equals(annotation.loggingType())) {
            throw new IllegalArgumentException("注解[" + ServerHandler.class.getSimpleName() + "]属性设置有误，reParse为true时必须保存报文记录");
        }
        logger.info("当前交易为[{}]", annotation.msgName());
        transComponent.setSuccess(false);
        transComponent.setSysCode(annotation.sysCode());
        transComponent.setMsgCode(annotation.msgCode());
        transComponent.setMsgName(annotation.msgName());
        transComponent.setLoggingType(annotation.loggingType());
        transComponent.setUnRepeatable(annotation.unRepeatable());
        transComponent.setReParse(annotation.reParse());
        this.component = transComponent;
    }

    /**
     * 加锁
     *
     * @return left：lock_key，right：lock_value
     */
    protected ImmutablePair<String, String> lock(String lockKey) {
        final long lockTimeout = 300L;
        String lockValue = UUID.randomUUID().toString();
        if (distributedLockService.lock(lockKey, lockValue, lockTimeout)) {
            return ImmutablePair.of(lockKey, lockValue);
        }
        throw new IllegalStateException("交易处理中，请勿重复提交");
    }

    /**
     * 解锁
     *
     * @param lockPair left：lock_key，right：lock_value
     * @return true-解锁成功，false-解锁失败
     */
    protected boolean unlock(ImmutablePair<String, String> lockPair) {
        return distributedLockService.unlock(lockPair.getLeft(), lockPair.getRight());
    }

    /**
     * 解析请求报文
     *
     * @return lock_key，用于加锁，为null时不加锁
     */
    protected abstract String parseRequestMsg();

    /**
     * 保存交易记录
     */
    private void writeAccountLog() {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        template.executeWithoutResult((status) -> {
            AccountLog accountLog = new AccountLog();
            accountLog.setContractId(component.getContractId());
            accountLog.setDetailId(component.getDetailId());
            accountLog.setContractNo(component.getContractNo());
            accountLog.setProductNo(component.getProductNo());
            accountLog.setTranDate(this.tranDate);
            accountLog.setTranTime(this.tranTime);
            accountLog.setSysCode(component.getSysCode());
            accountLog.setMsgCode(component.getMsgCode());
            accountLog.setTranStatus(TransConstant.TRAN_STATUS_U);
            accountLog.setReversalFlag(getReversalFlag());
            accountLog.setCreateTime(DateUtil.getDateTime());
            accountLogMapper.insertSelective(accountLog);
            currAccountLog = accountLog;

            AccountMsgLog accountMsgLog = new AccountMsgLog();
            accountMsgLog.setAcctId(currAccountLog.getId());
            accountMsgLog.setTranDate(currAccountLog.getTranDate());
            accountMsgLog.setTranTime(currAccountLog.getTranTime());
            accountMsgLog.setTranStatus(currAccountLog.getTranStatus());
            accountMsgLog.setReqSeqNo(component.getReqSeqNo());
            accountMsgLog.setCreateTime(DateUtil.getDateTime());
            if (ClientHandler.LoggingType.TABLE.equals(component.getLoggingType())) {
                accountMsgLog.setReqMsg(component.getReqMsg());
            }
            accountMsgLogMapper.insertSelective(accountMsgLog);
            currAccountMsgLog = accountMsgLog;
        });

        if (ClientHandler.LoggingType.FILE.equals(component.getLoggingType())) {
            FileUtil.writeUtf8String(component.getReqMsg(), getLogFilePath(currAccountMsgLog.getTranDate(), currAccountLog.getSysCode(), currAccountLog.getMsgCode(), currAccountMsgLog.getId()));
        }
    }


    /**
     * 成功检查，判断是否已成功
     *
     * @return true-已成功，false-未成功
     */
    private boolean succeed() {
        AccountMsgLogExample msgLogExample = new AccountMsgLogExample();
        msgLogExample.createCriteria().andReqSeqNoEqualTo(component.getReqSeqNo());
        msgLogExample.setOrderByClause("ID DESC");
        List<AccountMsgLog> accountMsgLogList = accountMsgLogMapper.selectByExampleWithBLOBs(msgLogExample);
        if (CollectionUtils.isEmpty(accountMsgLogList)) {
            return isTranSucceed();
        }
        AccountMsgLog accountMsgLog = accountMsgLogList.get(0);
        AccountLog accountLog = accountLogMapper.selectByPrimaryKey(accountMsgLog.getAcctId());
        this.lastAccountLog = accountLog;
        this.lastAccountMsgLog = accountMsgLog;
        if (TransConstant.TRAN_STATUS_S.equals(accountLog.getTranStatus())) {
            return true;
        } else if (TransConstant.TRAN_STATUS_F.equals(accountLog.getTranStatus())) {
            return false;
        }
        return isTranSucceed(accountLog, accountMsgLog);
    }

    /**
     * 判断交易是否已成功
     *
     * @param accountLog    根据请求流水号查询到的交易记录
     * @param accountMsgLog 根据请求流水号查询到的交易报文记录
     * @return true-已成功，false-未成功
     */
    protected abstract boolean isTranSucceed(AccountLog accountLog, AccountMsgLog accountMsgLog);

    /**
     * 判断交易是否已成功
     * <p>
     * 根据流水号查询不到交易记录
     *
     * @return true-已成功，false-未成功
     */
    protected abstract boolean isTranSucceed();


    /**
     * 重新解析响应报文
     */
    private void reParseRequestMsg() {
        // 查询报文记录
        if (StringUtils.isEmpty(component.getRespMsg())) {
            Assert.notNull(lastAccountLog, "获取交易记录为空");
            if (ClientHandler.LoggingType.NONE.equals(component.getLoggingType())) {
                throw new IllegalArgumentException("当前交易不记录报文，不能重新解析");
            }
            Assert.isTrue(TransConstant.TRAN_STATUS_S.equals(lastAccountMsgLog.getTranStatus()), "交易状态不正确，应为S");
            if (ClientHandler.LoggingType.TABLE.equals(component.getLoggingType())) {
                Assert.hasText(lastAccountMsgLog.getRespMsg(), "获取响应报文为空，不能重新解析");
                component.setRespMsg(lastAccountMsgLog.getRespMsg());
            } else if (ClientHandler.LoggingType.FILE.equals(component.getLoggingType())) {
                String respMsg = FileUtil.readUtf8String(getLogFilePath(lastAccountMsgLog.getTranDate(), lastAccountLog.getSysCode(), lastAccountLog.getMsgCode(), lastAccountMsgLog.getId()));
                Assert.hasText(respMsg, "获取报文文件内容为空，不能重新解析");
                String[] msgArr = StringUtils.split(respMsg, "\n\n");
                Assert.isTrue(msgArr != null && msgArr.length == 2, "解析报文文件出错，格式不正确");
                respMsg = msgArr[1];
                Assert.hasText(respMsg, "获取响应报文为空，不能重新解析");
                component.setRespMsg(respMsg);
            } else {
                throw new IllegalArgumentException("未知的报文记录方式：" + component.getLoggingType());
            }
        }

        // 解析响应报文
        parseRequestMsg();
    }

    /**
     * 处理业务逻辑
     */
    protected abstract IBody doService();

    /**
     * 组装响应报文
     */
    protected abstract void buildResponseMsg(IBody responseBody);

    /**
     * 更新交易记录
     */
    private void updateAccountLog(Throwable th) {
        if (currAccountLog == null) {
            logger.warn("当前交易记录为空");
            return;
        }
        String tranStatus = null;
        if (beforeDoService) {
            // 业务处理及处理前，就报错了
            tranStatus = TransConstant.TRAN_STATUS_F;
        } else {
            if (th == null) {
                if (component.isSuccess()) {
                    tranStatus = TransConstant.TRAN_STATUS_S;
                } else {
                    tranStatus = TransConstant.TRAN_STATUS_F;
                }
            } else {
                tranStatus = TransConstant.TRAN_STATUS_U;
            }
        }

        AccountLog accountLog = new AccountLog();
        accountLog.setId(currAccountLog.getId());
        accountLog.setTranStatus(tranStatus);
        accountLog.setUpdateTime(DateUtil.getDateTime());
        accountLogMapper.updateByPrimaryKeySelective(accountLog);

        if (currAccountMsgLog == null) {
            logger.warn("当前交易报文记录为空");
            return;
        }
        AccountMsgLog accountMsgLog = new AccountMsgLog();
        accountMsgLog.setId(currAccountMsgLog.getId());
        accountMsgLog.setTranStatus(tranStatus);
        accountMsgLog.setRetCode(component.getRetCode());
        if (StringUtils.isEmpty(component.getRetMsg())) {
            if (th != null) {
                component.setRetMsg(ExceptionUtils.getRootCauseMessage(th));
            } else if (component.isSuccess()) {
                component.setRetMsg(GxpConstant.RET_MSG_SUCCESS);
            } else {
                component.setRetMsg("交易失败，未知原因");
            }
        }
        accountMsgLog.setRetMsg(component.getRetMsg());
        accountMsgLog.setRespSeqNo(component.getRespSeqNo());
        accountMsgLog.setUpdateTime(DateUtil.getDateTime());
        if (ClientHandler.LoggingType.TABLE.equals(component.getLoggingType())) {
            accountMsgLog.setRespMsg(component.getRespMsg());
        }
        accountMsgLogMapper.updateByPrimaryKeySelective(accountMsgLog);

        if (component.isReversal()) {
            // 当前交易为冲正交易
            logger.info("当前交易为冲正交易");
            // 查询原记录，更新状态为以冲正
            AccountMsgLogExample msgLogExample = new AccountMsgLogExample();
            msgLogExample.createCriteria().andReqSeqNoEqualTo(component.getOrgReqSeqNo()).andTranStatusEqualTo(TransConstant.TRAN_STATUS_S);
            List<AccountMsgLog> accountMsgLogList = accountMsgLogMapper.selectByExample(msgLogExample);
            if (!CollectionUtils.isEmpty(accountMsgLogList)) {
                logger.info("更新正向交易状态");
                AccountMsgLog orgAccountMsgLog = accountMsgLogList.get(0);

                AccountLog logRecord = new AccountLog();
                logRecord.setId(orgAccountMsgLog.getAcctId());
                logRecord.setTranStatus(TransConstant.TRAN_STATUS_R);
                logRecord.setUpdateTime(DateUtil.getDateTime());
                accountLogMapper.updateByPrimaryKeySelective(logRecord);

                AccountMsgLog msgLogRecord = new AccountMsgLog();
                msgLogRecord.setId(orgAccountMsgLog.getId());
                msgLogRecord.setTranStatus(TransConstant.TRAN_STATUS_R);
                msgLogRecord.setUpdateTime(DateUtil.getDateTime());
                accountMsgLogMapper.updateByPrimaryKeySelective(msgLogRecord);
            }
        }

        if (ClientHandler.LoggingType.FILE.equals(component.getLoggingType())) {
            FileWriter.create(new File(getLogFilePath(currAccountMsgLog.getTranDate(), currAccountLog.getSysCode(), currAccountLog.getMsgCode(), currAccountMsgLog.getId())), CharsetUtil.CHARSET_UTF_8).append("\n\n" + component.getRespMsg());
        }
    }

    /**
     * 报文记录到文件时，获取文件路径
     *
     * @param tranDate     交易日期
     * @param sysCode      系统编码
     * @param msgCode      消息编码
     * @param accountMsgId ACCOUNT_MSG_LOG.ID
     * @return
     */
    private String getLogFilePath(String tranDate, String sysCode, String msgCode, Integer accountMsgId) {
        return "/home/ssm/logs/trans/" + tranDate + File.separator + sysCode + File.separator + msgCode + "_" + accountMsgId + ".txt";
    }

    /**
     * 获取冲正标志
     *
     * @return 0-记账交易，1-冲正交易
     */
    private String getReversalFlag() {
        if (component.isReversal()) {
            return TransConstant.IS_FLAG_1;
        } else {
            return TransConstant.IS_FLAG_0;
        }
    }

}
